<script setup>
import { reactive, computed, watch, onMounted, unref, toRef } from 'vue'
import { useDesign } from '@/hooks/web/useDesign'

const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('count-to')

const props = defineProps({
  startVal: { type: Number, default: 0 },
  endVal: { type: Number, default: 2021 },
  duration: { type: Number, default: 3000 },
  autoplay: { type: Boolean, default: true },
  decimals: { type: Number, default: 0 },
  decimal: { type: String, default: '.' },
  separator: { type: String, default: ',' },
  prefix: { type: String, default: '' },
  suffix: { type: String, default: '' },
  useEasing: { type: Boolean, default: true },
  easingFn: {
    type: Function,
    default(t, b, c, d) {
      return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
    }
  }
})

const emit = defineEmits(['mounted', 'callback'])

function formatNumber(num) {
  const { decimals, decimal, separator, suffix, prefix } = props
  num = Number(num).toFixed(decimals)
  const x = num.split('.')
  let x1 = x[0]
  const x2 = x.length > 1 ? decimal + x[1] : ''
  const rgx = /(\d+)(\d{3})/
  while (separator && rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + separator + '$2')
  }
  return prefix + x1 + x2 + suffix
}

const state = reactive({
  localStartVal: props.startVal,
  displayValue: formatNumber(props.startVal),
  printVal: null,
  paused: false,
  localDuration: props.duration,
  startTime: null,
  timestamp: null,
  remaining: null,
  rAF: null
})

const displayValue = toRef(state, 'displayValue')

onMounted(() => {
  if (props.autoplay) start()
  emit('mounted')
})

const getCountDown = computed(() => props.startVal > props.endVal)

watch([() => props.startVal, () => props.endVal], () => {
  if (props.autoplay) start()
})

function start() {
  state.localStartVal = props.startVal
  state.startTime = null
  state.localDuration = props.duration
  state.paused = false
  state.rAF = requestAnimationFrame(count)
}

function pauseResume() {
  if (state.paused) {
    resume()
    state.paused = false
  } else {
    pause()
    state.paused = true
  }
}

function pause() {
  cancelAnimationFrame(state.rAF)
}

function resume() {
  state.startTime = null
  state.localDuration = +state.remaining
  state.localStartVal = +state.printVal
  requestAnimationFrame(count)
}

function reset() {
  state.startTime = null
  cancelAnimationFrame(state.rAF)
  state.displayValue = formatNumber(props.startVal)
}

function count(timestamp) {
  const { useEasing, easingFn, endVal } = props
  if (!state.startTime) state.startTime = timestamp
  state.timestamp = timestamp
  const progress = timestamp - state.startTime
  state.remaining = state.localDuration - progress

  if (useEasing) {
    state.printVal = getCountDown.value
      ? state.localStartVal - easingFn(progress, 0, state.localStartVal - endVal, state.localDuration)
      : easingFn(progress, state.localStartVal, endVal - state.localStartVal, state.localDuration)
  } else {
    state.printVal = getCountDown.value
      ? state.localStartVal - (state.localStartVal - endVal) * (progress / state.localDuration)
      : state.localStartVal + (endVal - state.localStartVal) * (progress / state.localDuration)
  }

  // 边界限制
  if (getCountDown.value) {
    state.printVal = Math.max(state.printVal, endVal)
  } else {
    state.printVal = Math.min(state.printVal, endVal)
  }

  state.displayValue = formatNumber(state.printVal)

  if (progress < state.localDuration) {
    state.rAF = requestAnimationFrame(count)
  } else {
    emit('callback')
  }
}

defineExpose({
  pauseResume,
  reset,
  start,
  pause
})
</script>

<template>
  <span :class="prefixCls">
    {{ displayValue }}
  </span>
</template>
